/*
 * GeoVISTA Center (Penn State, Dept. of Geography)
 * Copyright (c), 2003-2011,  Jin Chen and Alan M. MacEachren, GeoVISTA Center, Penn State University
 * All Rights Researved.
 *
 * This library is developed and distributed for free usage,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * You can redistribute and/or modify it under the terms of the
 * Simplified BSD License
 *
 *
 *
 *
 *
 *
 *
 *
 * @author: jin Chen
 * @date: Jun 16, 2003
 * @version: $VERSION$
 */
package edu.psu.geovista.app.pcp.plot.layer;


import edu.psu.geovista.app.plot.axis.AxisList;
import edu.psu.geovista.app.plot.axis.XAxis;
import edu.psu.geovista.app.plot.axis.YAxis;
import edu.psu.geovista.common.debug.Debug;

import java.awt.*;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.util.Calendar;
import java.util.StringTokenizer;

public class DataPainter {
    public static final boolean DEBUG = false;
    protected edu.psu.geovista.data.model.AppDataModel dataModel;
    protected AxisList yaxisList;
    protected XAxis xaxis;
    //protected MultiYPlot plot;
    private Color defaultColor=new Color(0,128,128,255);
    private Color[] colors;//classification colors
    private float value;

    protected int[] recordsToShow; //The id of records to be shown, if null show nothing; if array.length=0, show nothing
    private boolean antialising=false;//
    private Stroke defaultStroke=new BasicStroke(1.0f);
    //private Stroke hightLightStroke=new BasicStroke(2.5f);
    private Stroke dashLineStroke=new BasicStroke(0.5f,BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL,0,new float[]{12,12},0); //for draw dash line
    private Composite dashLineComposite=AlphaComposite.getInstance(AlphaComposite.SRC_OVER ,1f/*DataLayer.DataBackgroundLightness*/) ;
    private boolean invalidDataShown;
    
    public DataPainter(AxisList yaxisList,XAxis xaxis,edu.psu.geovista.data.model.AppDataModel dataModel) {
        //this.plot=pcp;
        this.dataModel = dataModel;
        this.yaxisList = yaxisList;
        this.xaxis =xaxis;
        recordsToShow=(this.dataModel).getAllRowIndexs() ;
    }

   /*******************************************************************************************************
    *                services
    *******************************************************************************************************/
   /**************************************************
    *                draw line data  across all axes
    ***************************************************/
    public   void  drawData(Graphics g){
         long start,stop; //put is at start of method
                 if(DEBUG){//put is at start of method
                     start=Calendar.getInstance().getTime().getTime() ;
                 }

         Graphics2D g2d=(Graphics2D)g;
         //RenderableGraphics g2d=(RenderableGraphics)g;
         Stroke oldStroke=g2d.getStroke() ;
         g2d.setStroke(this.defaultStroke );
         if(this.getColors() ==null){
            g2d.setColor(this.getDefaultColor());
         }
        g2d.setStroke(this.defaultStroke );
         //RenderingHints oldRH=g2d.getRenderingHints() ;
         if(this.isAntialising()){
             g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
         }
         int numOfRecord=this.dataModel.getRowCount() ;
         //int numOfAxis=this.dataModel.getNumDimensions() ;
         int numOfAxis=this.yaxisList.size() ;
         if(g2d!=null){
             if (this.recordsToShow ==null) {
                 return;

             }
             else{
                 yaxisList.updateDisplayIndexList();
                 for (int i=0;i<recordsToShow.length ;i++){
                     if(DEBUG)
                     System.out.println("DataPainter.paint Record:"+i);
                     this.drawARecord(g2d,recordsToShow[i],0,numOfAxis-1);
                 }
             }

         }
        g2d.setStroke(oldStroke);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
        if(DEBUG){  //put is at end of method
                     stop=Calendar.getInstance().getTime().getTime() ;

                     System.out.println("drawLines()"+numOfRecord+" take "+(stop-start)*0.001f+" to finish" );
        }
    }

  /**************************************************
   *                   draw line data btw any 2 axes
   ***************************************************/
     /**
     *  draw line to link all the axises between "from" axis and "to" axis
     */
    public  void linkAxis(Graphics2D g2d,int from, int to) {
        long start,stop; //put is at start of method
        if (DEBUG) {//put is at start of method
            start = Calendar.getInstance().getTime().getTime();
        }

        //Graphics2D g2d = this.getImagePainter();
        //RenderableGraphics g2d=(RenderableGraphics)g;
        if(this.getColors() ==null){
            Color dfcolor = this.getDefaultColor();
            g2d.setColor(dfcolor );
        }
         g2d.setStroke(this.defaultStroke );
         //RenderingHints oldRH=g2d.getRenderingHints() ;
         if(this.isAntialising()){
             g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
         }
        int numOfRecord = this.dataModel.getRowCount();
        //int numOfAxis=this.dataModel.getNumDimensions() ;
        if (g2d != null) {
            if (this.recordsToShow ==null) {
                return;
                /*for (int i = 0; i < numOfRecord; i++) { //i: record(row) index
                    this.drawARecord(g2d,i,from,to);

                }*/
            }
            else{
                for (int i=0;i<recordsToShow.length ;i++){
                     this.drawARecord(g2d,recordsToShow[i],from,to);
                }
            }

        }
      

        if (DEBUG) {  //put is at end of method
            stop = Calendar.getInstance().getTime().getTime();

            System.out.println("drawLineBetweenAxis()" + numOfRecord + " take " + (stop - start) * 0.001f + " to finish");
        }

    }
   /*******************************************************************************************************
    *                Highlight record
    *******************************************************************************************************/
   public void drawAHighlightRecord(Graphics2D g2d, int recordID,  boolean drawlabel) {
       drawAHighlightRecord(g2d,recordID,0,this.yaxisList.size()-1,drawlabel);

   }
    /**
    *  draw a single record from AxisFrom(with getIndex()=from) to AxisTo(with getIndex()=to)
    * @param g2d
    * @param recordID  record id
    * @param from
    * @param to
    * @param drawlabel
    */
    public void drawAHighlightRecord(Graphics2D g2d, int recordID, int from, int to, boolean drawlabel) {

                Color col =  new Color(1.0f, 1.0f, 0.8f);
                Font oldfont = g2d.getFont();
                Font newfont = new Font(oldfont.getName(), Font.PLAIN, oldfont.getSize() - 2);
                g2d.setFont(newfont);

                GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);

          /*      g2d.setStroke(new BasicStroke(2.5f));
            g2d.draw(path);
*/          g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON );
            g2d.setStroke(new BasicStroke(2.5f));  //width of line
            //* draw the line
            this.assemblePath(g2d,path,recordID,  from, to);

            path.reset();
            g2d.setFont(oldfont);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF );

            //* labels
            if(drawlabel){
            col =  new Color(1.0f, 1.0f, 0.8f);     //Color.white ;//
           float[] record = (float[]) dataModel.getRowValueAsFloat(recordID);
            for (int j=from; j<=to; j++){
                YAxis axis = this.yaxisList.getAxis(j);  //axis
                int rawIndex=axis.getDataIndex() ;//j is display index of axis
                float dataValue=record[rawIndex];
                if (Float.isNaN(dataValue) ) continue;
                //Location to draw tooltip
                int x=axis.getLocation().x;
                int y = axis.transform(dataValue);
                String s = ""+dataValue;  // axis.getName() + "=\n" +
                drawTooltip(g2d,s, x , y, col, this.xaxis.getActiveSize());
            }
            }

    }
    /**
     *  
     * @param g2d
     * @param metaText
     * @param metaX
     * @param metaY
     */
    public void drawHighlightRecordName(Graphics2D g2d,String metaText,int metaX, int metaY) {
        if(metaText!=null)
            drawTooltip(g2d, metaText, metaX, metaY + 10, new Color(0.7f, 0.7f, 1.0f), this.xaxis.getActiveSize());
        else{
            System.out.println("Record label not available");
            //log
        }
    }
    /**
     * Helper function to draw a "tooltip" on the given graphics object.
     *
     * @param g2 The Graphics2D Object to draw on.
     * @param text The (multiline) text of the tooltip.
     * @param x The x coordinate.
     * @param y The y coordinate.
     * @param col The background color.
     * @param width
     */
    public static void drawTooltip(Graphics2D g2, String text, int x, int y, Color col, int width){
        int i;
        int mheight, mwidth = 0;
        int numLines, lineHeight;
        //int width=this.xaxis.getActiveSize() ;
        StringTokenizer tok = new StringTokenizer(text,"\n");
        numLines = tok.countTokens();
        String lines[] = new String[numLines];

        for (i=0; i<numLines; i++){
            lines[i] = tok.nextToken();

            int tempwidth =  g2.getFontMetrics().stringWidth(lines[i]) + 6;
            if (tempwidth > mwidth) mwidth = tempwidth;
        }

        lineHeight = g2.getFontMetrics().getHeight();
        mheight = numLines * lineHeight + 2;

        x += 10;
        y += 10;
        if (x + mwidth > width) x -= (mwidth + 20);

        //Composite oldComp = g2.getComposite();
        //draw rectangle
        AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f);
        //g2.setComposite(ac); // 

        g2.setStroke(new BasicStroke(0.5f));
        g2.setColor(new Color(0.2f, 0.2f, 0.2f));
        g2.drawRect(x, y, mwidth, mheight);
        g2.setColor(col);
        g2.fillRect(x+1, y+1, mwidth-1, mheight-1);

        //draw text
        g2.setColor(Color.black);
        ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER);
        //g2.setComposite(ac);// 
        for (i=0; i<numLines; i++){
            g2.drawString(lines[i], x + 3, y + (i+1) * lineHeight -1);
        }
       // g2.setComposite(oldComp);

    }


    /*******************************************************************************************************
     *                support
     *******************************************************************************************************/

   /**
    *  draw a single record from AxisFrom(with getIndex()=from) to AxisTo(with getIndex()=to)
    * @param g2d
    * @param recordID  record id
    * @param from
    * @param to
    */
    private void drawARecord(Graphics2D g2d, int recordID, int from,int to) {
               // boolean debug=false;

       Color[] colors = this.getColors();
       if (colors !=null&&colors.length >0){
                    Color color=this.getColors()[recordID] ;
                    g2d.setColor(color);
                }

                GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
                this.assemblePath(g2d,path,recordID,  from, to);
                //g2d.draw(path);
                path.reset();

    }

 private GeneralPath assemblePath(Graphics2D g2d,GeneralPath path, int recordID,int from,int to){

         float[] record = (float[]) dataModel.getRowValueAsFloat(recordID);
         //System.out.println("draw record:"+record);
         //The prerequisition for drawing segment is that both 2 consecutive axis can
                //provide Not-NaN value
                YAxis axis;
                float dataValue;

                boolean axisFromValid=false; //true means current axis can provide Not-NaN value
                for (int j = from ; j <= to; j++) { //j: axis(column) index
                    //end
                    //YAxis axe = (YAxis) axisList.get(j);
                    axis = this.yaxisList.getAxis(j);  //axis
                    int rawIndex=axis.getDataIndex() ;//j is display index of axis
                    dataValue=record[rawIndex];


                    if(Float.isNaN(dataValue)||(int)dataValue==edu.psu.geovista.data.model.AppDataModel.NULL_INT ){
                        axisFromValid=false; //
                        continue;
                    }
                    else{// axis can provide a valid value(Not NaN) for the record

                        int x = axis.getLocation().x;
                        int y = axis.transform(dataValue);
                        if(x<0 ||y<0){
                            // 
                            // 
                            String s = this.getClass().getName() +".assemblePath() draw negative values and the data won't be able to be dispalyed";
                            Debug.debug(DEBUG, s);
                            //System.err.println(this.getClass().getName() +".assemblePath() draw negative values and the data won't be able to be dispalyed");
                        }
                        if(!axisFromValid){// the axis act as axisFrom
                            /* 
                             * 
                             * 
                             * 
                             * 
                             */
                            Point2D p=path.getCurrentPoint();
                            int fx,fy;
                            boolean firstPoint=true;
                            if(p!=null){//At fist point, p==null
                               fx=(int) path.getCurrentPoint().getX() ;
                               fy=(int) path.getCurrentPoint().getY() ;
                                firstPoint=false;
                            }
                            else{
                                fx=Integer.MIN_VALUE ;
                                fy=Integer.MIN_VALUE ;
                            }
                           path.moveTo(x,y);
                           if(!firstPoint&&isInvalidDataShown()){
                               this.drawDashLine(g2d,fx,fy,x,y);
                           }
                           axisFromValid=true;
                        }
                        else{//// the axis act as axisTo
                            path.lineTo(x,y);
                        }//else
                    } //else
                }//for
        g2d.draw(path);
        return path;
    }

    private void drawDashLine(Graphics2D g2d, int fromx, int fromy,int tox, int toy){
         Stroke old=g2d.getStroke() ;
         Composite comp=g2d.getComposite() ;
         g2d.setStroke(this.dashLineStroke );
         g2d.setComposite(dashLineComposite);
         g2d.drawLine(fromx,fromy,tox,toy);
         g2d.setStroke(old);
         g2d.setComposite(comp);
    }

    /*******************************************************************************************************
     *                properties
     *******************************************************************************************************/
    public Color getDefaultColor() {
        return defaultColor;
    }

    public void setDefaultColor(Color defaultColor) {
        this.defaultColor = defaultColor;
    }
    /**
     * The id of records to be shown, if null show all; if array.length=0, show nothing
     * @param recordsToShow
     */
    public void setRecordsToShow(int[] recordsToShow) {
        this.recordsToShow = recordsToShow;
    }

    public Color[] getColors() {
        return colors;
    }

    public void setColors(Color[] colors) {
        this.colors = colors;
    }

    public boolean isInvalidDataShown() {
        return invalidDataShown;
    }

    public void setInvalidDataShown(boolean invalidDataShown) {
        this.invalidDataShown = invalidDataShown;
    }

    public Stroke getDashLineStroke() {
        return dashLineStroke;
    }

    public void setDashLineStroke(Stroke dashLineStroke) {
        this.dashLineStroke = dashLineStroke;
    }

    public Composite getDashLineComposite() {
        return dashLineComposite;
    }

    public void setDashLineComposite(Composite dashLineComposite) {
        this.dashLineComposite = dashLineComposite;
    }

    public void setDataModel(edu.psu.geovista.data.model.AppDataModel dataModel) {
        this.dataModel = dataModel;
        recordsToShow=((edu.psu.geovista.data.model.AppDataModel)this.dataModel).getAllRowIndexs() ;

    }

    public void setDefaultStroke(Stroke defaultStroke) {
        this.defaultStroke = defaultStroke;
    }

    public void setAntialising(boolean antialising) {
        this.antialising = antialising;
        if(antialising){
            //just a warning
            System.err.println(" AntiAlising is turned on, the rendering performance will be significantly slow");
        }
    }

    public boolean isAntialising() {
        return antialising;
    }
}

